#include <utility>		//std::forward

void test_case1_decltypeGiveWhatExpected()
{
    const int& i = 0;
    decltype(i) dec_i = 0;  //dec_i is const int&, keep all pieces. 
    //decltype(auto) da_i = i;   //c++14, keep da_t same type as i.
    
    //revisit the auto deduction.
    auto a_i = i;           //a_i is int;   pass by value, reference and const are both ignored.
    auto& ar_i= i;          //ar_i is const int&; const is preserved, reference is ignored, but auto has one.

    //different from auto/template deduction, decltype will keep as same type of the name or expression as possible 
}

//return type will be the same as operator[] of c.
template<typename Container, typename Index>
auto case2_1_element(Container& c, Index i) -> decltype(c[i])
{
    return c[i];
}

//std::forward makes sure rvalue becomes rvalue reference, const lvalue becomes const lvalue reference
//and non-const lvalue becomes non-const lvalue reference (details see Item 27).
template<typename Container, typename Index>
auto case2_2_element(Container&& c, Index i) -> decltype(std::forward<Container>(c)[i])
{
    return std::forward<Container>(c)[i];
}

//c++14 version
//decltype(auto) will keep the exact type of operator[] to be the return type,
//without it, the reference will be ignored, since return type deduction is using
//template deduction rules. 
/*
template<typename Container, typename Index>
decltype(auto) case2_3_element(Container&& c, index i)
{
    return std::forward<Container>(c)[i];
}
*/

#include <vector>
void test_case2_decltypeForReturnType()
{
    std::vector<int> v(10, 1);

    auto e = case2_1_element(v, 1);     //return type is int&, e is int
    decltype(case2_1_element(v, 1)) e1 = case2_1_element(v, 1); //more clear with decltype, int&

    case2_1_element(v, 1) = 11;

    // auto e2 = case2_1_element(std::vector<int>(10,1), 1); //error, rvalue can not bind to lvalue reference.
                                                             //visual studio 2010 does not fail it, :(
    //use universal reference to make both lvalue and rvalue work.
    auto e3 = case2_2_element(std::vector<int>(10,1), 1);   //now rvalue is fine. 
}

/*
c++14 only
decltype(auto) case3_f1()
{
    int x=0;
    ...
    return x;   // decltype(x) is int, so f1 returns int
}

decltype(auto) case3_f2()
{
    int x=0;
    ...
    return (x); // decltype((x)) is int&, so f2 returns int&
}
*/

void test_case3_cxx14_decltype_auto()
{
    //c++14 supports decltype(auto).
    //if it is used on return type, retuen v is quite different from return (v);
    //The second is a lvalue expression, so decltype will yield lvalue reference. 
}

int Test_I03_Understand_decltype(int, char**)
{
    test_case1_decltypeGiveWhatExpected();
    test_case2_decltypeForReturnType();
    test_case3_cxx14_decltype_auto();

    return 0;
}

/*
Things to Remember
- decltype almost always yields the type of a variable or expression without
  any modifications.
- For lvalue expressions of type T other than names, decltype always reports a
  type of T&.
- C++14 supports decltype(auto), which, like auto, deduces a type from its
  initializer, but it performs the type deduction using the decltype rules.
*/
